import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:path/path.dart' as Path;

import 'AbsBaseBean.dart';
import 'JsonClass.dart';

TypeChecker get _jsonTypeChecker => TypeChecker.fromRuntime(_AllowedJsonType);
TypeChecker get _jsonClassChecker => TypeChecker.fromRuntime(JsonClass);

Map<DartType, String> _allowedTypes = {};
List<ClassElement> _jsonClassElements = [];
List<String> _imparts = [];

class JsonLibraryBuilder extends Builder {
  @override
  FutureOr<void> build(BuildStep buildStep) async {
    var libraryElement = await buildStep.inputLibrary;
    LibraryReader libraryReader = LibraryReader(libraryElement);

    if (!await _hasAnyTopLevelAnnotations(buildStep.inputId, buildStep.resolver, buildStep)) return;

    //jsonClass标记的类型
    for (var annotatedElement in libraryReader.annotatedWith(_jsonClassChecker)) {
      var element = annotatedElement.element;
      if (element is ClassElement) {
        _jsonClassElements.add(element);
      }
      _imparts.add(buildStep.inputId.path);
    }

    //可生成json的字段类型
    for (var annotatedElement in libraryReader.annotatedWith(_jsonTypeChecker)) {
      var element = annotatedElement.element;
      if (element is VariableElement) {
        var entries = element.computeConstantValue()?.toMapValue()?.entries;
        if (entries != null) {
          for (var entrie in entries) {
            var key = entrie.key?.type;
            if (key is ParameterizedType) {
              var mtype = key.typeArguments[0];
              var value = entrie.value?.toStringValue();
              if (value != null) _allowedTypes[mtype] = value;
            }
          }
        }
      }
    }
  }


  @override
  Map<String, List<String>> get buildExtensions => {
        '.dart': [".g2.dart"]
      };
}

class JsonClassBaseGenerator extends GeneratorForAnnotation<JsonClassBase> {
  late LibraryElement libraryElement;

  @override
  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
    libraryElement = await buildStep.inputLibrary;
    return await super.generate(library, buildStep);
  }

  @override
  generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
    List<String> codeLines = [];
    codeLines.add("import 'package:decimal/decimal.dart';");
    codeLines.add("import 'BeanTrans.dart';");
    codeLines.add("import 'BaseBean.dart';");

    for (var _import in _imparts) {
      var path = Path.posix.relative(_import, from: Path.posix.dirname(buildStep.inputId.path));
      codeLines.add("import '$path';");
    }
    codeLines.addAll(generateFunctionsCode());
    return codeLines.join();
  }

  List<String> generateFunctionsCode() {
    List<String> toBeanCodeLines = [];
    List<String> toMapCodeLines = [];
    List<String> buildTypeCodeLines = [];
    List<String> transMapCodeLines = [];

    buildTypeCodeLines.add("T generateBeanObject<T>(){");
    buildTypeCodeLines.add("switch(T){");

    transMapCodeLines.add("initTransBeanMap(){");
    for (var element in _jsonClassElements) {
      toBeanCodeLines.add("jsonTo${element.displayName}(BaseBean bean,Map<String,dynamic> json){");
      toBeanCodeLines.add("if(bean is ${element.displayName}){");

      toMapCodeLines.add("${element.displayName}ToJson(BaseBean bean,Map<String,dynamic> json){");
      toMapCodeLines.add("if(bean is ${element.displayName}){");

      buildTypeCodeLines.add("case ${element.displayName}:return ${element.displayName}() as T;");

      transMapCodeLines.add("mapTarnsToBean[\"${element.displayName}\"] = jsonTo${element.displayName};");
      transMapCodeLines.add("beanTransToMap[\"${element.displayName}\"] = ${element.displayName}ToJson;");

      var fields = _getAllFields(element);
      for (var field in fields.values) {
        if (field.isStatic) continue;
        if (field.isPrivate) continue;

        String fieldName = field.displayName;
        var fieldType = field.type;
        String jsonKeyName = fieldName;
        List<String> alternateNames = [fieldName];

        for (var matedata in field.metadata) {
          if (matedata.toString().startsWith("@JsonField")) {
            var constantValue = matedata.computeConstantValue();
            var defaultName = constantValue?.getField("name")?.toStringValue();
            if (defaultName != null) {
              jsonKeyName = defaultName;
              alternateNames.insert(0, jsonKeyName);
            }

            var alternates = constantValue?.getField("alternate")?.toListValue();
            if (alternates != null) {
              for (var alternate in alternates) {
                var key = alternate.toStringValue();
                if (key != null) alternateNames.add(key);
              }
            }
          }
        }

        toMapCodeLines.addAll(generateTransToMapCode(fieldType, fieldName, jsonKeyName));

        if (field.isConst) continue;
        if (field.isFinal) continue;
        for (var alternateName in alternateNames.reversed) {
          toBeanCodeLines.addAll(generateTransCode(fieldType, fieldName, alternateName));
        }
      }
      toBeanCodeLines.add("}");
      toBeanCodeLines.add("}");

      toMapCodeLines.add("}");
      toMapCodeLines.add("}");
    }
    buildTypeCodeLines.add("}");
    buildTypeCodeLines.add("return BaseBean() as T;");
    buildTypeCodeLines.add("}");

    transMapCodeLines.add("}");

    return [...transMapCodeLines, ...buildTypeCodeLines, ...toBeanCodeLines, ...toMapCodeLines];
  }

  List<String> generateTransCode(DartType fieldType, String fieldName, String jsonKey) {
    List<String> codes = [];
    codes.add("{");
    codes.add("var value = json[\"$jsonKey\"];");
    codes.add("if(value!=null){");
    //codes.add("int tempValue = getValueToInt(value);");

    String? transCode;
    var nonNullType = libraryElement.typeSystem.promoteToNonNull(fieldType);
    for (var allowedType in _allowedTypes.entries) {
      var baseType = allowedType.key;
      if (libraryElement.typeSystem.isSubtypeOf(nonNullType, baseType)) {
        //if (nonNullType is TypeParameterType) {
        //  transCode = allowedType.value.replaceAll("{T}", nonNullType.bound.getDisplayString(withNullability: false));
        //} else {

        transCode = allowedType.value.replaceAll("{T}", nonNullType.getDisplayString(withNullability: false));
        //}

        if (baseType is InterfaceType) {
          if (baseType.isDartCoreMap && baseType.typeArguments[1].nullabilitySuffix == NullabilitySuffix.none) break;
          if (baseType.isDartCoreList && baseType.typeArguments[0].nullabilitySuffix == NullabilitySuffix.none) break;
        }
      }
    }
    if (transCode != null) codes.add(transCode);

    codes.add("if(tempValue!=null) bean.$fieldName=tempValue;");
    codes.add("}");
    codes.add("}");
    return codes;
  }
}

List<String> generateTransToMapCode(DartType fieldType, String fieldName, String jsonKey) {
  List<String> codes = [];
  codes.add("{");
  codes.add("dynamic tempValue = bean.$fieldName;");
  codes.add("if(tempValue is Decimal){");
  codes.add("json[\"$jsonKey\"] = toValueFromDecimal(tempValue);");
  codes.add("}else if(tempValue is BaseBean){");
  codes.add("json[\"$jsonKey\"] = toValueFromBean(tempValue);");
  codes.add("}else if(tempValue is Map<String,dynamic>){");
  codes.add("json[\"$jsonKey\"] = toValueFromMap(tempValue);");
  codes.add("}else if(tempValue is List<dynamic>){");
  codes.add("json[\"$jsonKey\"] = toValueFromList(tempValue);");
  codes.add("}else{");
  codes.add("json[\"$jsonKey\"] = tempValue;");
  codes.add("}");
  codes.add("}");
  return codes;
}

Future<bool> _hasAnyTopLevelAnnotations(AssetId input, Resolver resolver, BuildStep buildStep) async {
  if (!await buildStep.canRead(input)) return false;
  final parsed = await resolver.compilationUnitFor(input);
  final partIds = <AssetId>[];
  for (var directive in parsed.directives) {
    if (directive.metadata.isNotEmpty) return true;
    if (directive is PartDirective) {
      partIds.add(
        AssetId.resolve(Uri.parse(directive.uri.stringValue!), from: input),
      );
    }
  }
  for (var declaration in parsed.declarations) {
    if (declaration.metadata.isNotEmpty) return true;
  }
  for (var partId in partIds) {
    if (await _hasAnyTopLevelAnnotations(partId, resolver, buildStep)) {
      return true;
    }
  }
  return false;
}

Map<String, FieldElement> _getAllFields(ClassElement element) {
  Map<String, FieldElement> map = {};
  var superType = element.supertype;
  if (superType != null && !superType.isDartCoreObject) {
    var superMap = _getAllFields(superType.element);
    map.addAll(superMap);
  }
  for (var field in element.fields) {
    map[field.name] = field;
  }
  return map;
}

class _AllowedJsonType {
  const _AllowedJsonType();
}

class _TypeContainer<T> {
  const _TypeContainer();
}

@_AllowedJsonType()
const Map<_TypeContainer, String> allowedJsonType1 = {
  //基础类型
  _TypeContainer<int>(): "var tempValue = getValueToInt(value);",
  _TypeContainer<double>(): "var tempValue = getValueToDouble(value);",
  _TypeContainer<bool>(): "var tempValue = getValueToBool(value);",
  _TypeContainer<String>(): "var tempValue = getValueToString(value);",
  _TypeContainer<Decimal>(): "var tempValue = getValueToDecimal(value);",
  _TypeContainer<AbsBaseBean>(): "{T}? tempValue = getValueToBean(value);",

  //列表(成员不可为空)
  _TypeContainer<List<int>>(): "var tempValue = getValueToIntList(value);",
  _TypeContainer<List<double>>(): "var tempValue = getValueToDoubleList(value);",
  _TypeContainer<List<bool>>(): "var tempValue = getValueToBoolList(value);",
  _TypeContainer<List<String>>(): "var tempValue = getValueToStringList(value);",
  _TypeContainer<List<Decimal>>(): "var tempValue = getValueToDecimalList(value);",
  _TypeContainer<List<AbsBaseBean>>(): "{T}? tempValue = getValueToBeanList(value);",

  //列表(成员可为空)
  _TypeContainer<List<int?>>(): "var tempValue = getValueToNullableIntList(value);",
  _TypeContainer<List<double?>>(): "var tempValue = getValueToNullableDoubleList(value);",
  _TypeContainer<List<bool?>>(): "var tempValue = getValueToNullableBoolList(value);",
  _TypeContainer<List<String?>>(): "var tempValue = getValueToNullableStringList(value);",
  _TypeContainer<List<Decimal?>>(): "var tempValue = getValueToNullableDecimalList(value);",
  _TypeContainer<List<AbsBaseBean?>>(): "{T}? tempValue = getValueToNullableBeanList(value);",

  //map(成员不可为空)
  _TypeContainer<Map<String, int>>(): "var tempValue = getValueToIntMap(value);",
  _TypeContainer<Map<String, double>>(): "var tempValue = getValueToDoubleMap(value);",
  _TypeContainer<Map<String, bool>>(): "var tempValue = getValueToBoolMap(value);",
  _TypeContainer<Map<String, String>>(): "var tempValue = getValueToStringMap(value);",
  _TypeContainer<Map<String, Decimal>>(): "var tempValue = getValueToDecimalMap(value);",
  _TypeContainer<Map<String, AbsBaseBean>>(): "{T}? tempValue = getValueToBeanMap(value);",

  //map(成员可为空)
  _TypeContainer<Map<String, int?>>(): "var tempValue = getValueToNullableIntMap(value);",
  _TypeContainer<Map<String, double?>>(): "var tempValue = getValueToNullableDoubleMap(value);",
  _TypeContainer<Map<String, bool?>>(): "var tempValue = getValueToNullableBoolMap(value);",
  _TypeContainer<Map<String, String?>>(): "var tempValue = getValueToNullableStringMap(value);",
  _TypeContainer<Map<String, Decimal?>>(): "var tempValue = getValueToNullableDecimalMap(value);",
  _TypeContainer<Map<String, AbsBaseBean?>>(): "{T}? tempValue = getValueToNullableBeanMap(value);",
};
